/*
 * from https://blog.csdn.net/xreztento/article/details/74008959
 * gcc agent.c -I/usr/lib/jvm/java-8-oracle/include -I/usr/lib/jvm/java-8-oracle/include/linux -shared -fPIC -o ./libtestagent.so
 * java -jar target/oper-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod -agentpath:/home/sc/temp/libtestagent.so
*/

#include <stdio.h>
#include <memory.h>
#include <string.h>
#include <jvmti.h>
#include <stdlib.h>
#include <time.h>   

#define BUFLEN 255 
#define checkError(error,msg) if(error!= JVMTI_ERROR_NONE){printf("[agent] ERROR:%s, error code:%d\n",msg,error);exit(-1);}

size_t strftime(char *s, size_t max, const char *format,const struct tm *tm); 

void printFormat(){

    time_t t = time(0);   
    char tmpBuf[BUFLEN];   
    strftime(tmpBuf, BUFLEN, "%F %T", localtime(&t)); //format date and time. 
    printf("0000000000000000|DEBUG %s JVMTI | |Monitor|Thread.",tmpBuf);
}

void JNICALL
callbackThreadStart(jvmtiEnv *jvmti, JNIEnv* env, jthread thr){
    jvmtiThreadInfo info;
    jlong cpu_time;

    jvmtiError err;
    //获取启动线程信息
    (*jvmti)->GetThreadInfo(jvmti, thr, &info);

    printFormat();
    printf("start|%-60s|||||\n", info.name);
}

void JNICALL callbackThreadEnd(jvmtiEnv *jvmti,
            JNIEnv* env,
            jthread thr){

    jvmtiThreadInfo info;
    jlong cpu_time;

    //获取启动线程信息
    (*jvmti)->GetThreadInfo(jvmti, thr, &info);
    //获取启动线程CPU-Time
    (*jvmti)->GetThreadCpuTime(jvmti, thr, &cpu_time);

    printFormat();
    printf("end  |%-60s|cpu-time: %ld.%09ld s||||\n", info.name, cpu_time/1000000000, cpu_time%1000000000);
}

/*
void JNICALL
MonitorWait(jvmtiEnv *jvmti,
            JNIEnv* env,
            jthread thr,
            jobject object,
            jlong timeout){

    jvmtiThreadInfo info;

    //获取启动线程信息
    (*jvmti)->GetThreadInfo(jvmti, thr, &info);
    //printf("[%-60s] MonitorWait\n", info.name);

    (*jvmti)->Deallocate(jvmti, (unsigned char*)&info);
}

void JNICALL
MonitorWaited(jvmtiEnv *jvmti,
            JNIEnv* env,
            jthread thr,
            jobject object,
            jboolean timed_out){

    jvmtiThreadInfo info;

    //获取启动线程信息
    (*jvmti)->GetThreadInfo(jvmti, thr, &info);
    //printf("[%-60s] MonitorWaited\n", info.name);

    (*jvmti)->Deallocate(jvmti, (unsigned char*)&info);
}

void JNICALL
MonitorContendedEnter(jvmtiEnv *jvmti,
            JNIEnv* env,
            jthread thr,
            jobject object){

    jvmtiThreadInfo info;

    //获取启动线程信息
    (*jvmti)->GetThreadInfo(jvmti, thr, &info);
    //printf("[%-60s] MonitorContendedEnter\n", info.name);

    (*jvmti)->Deallocate(jvmti, (unsigned char*)&info);
}

void JNICALL
MonitorContendedEntered(jvmtiEnv *jvmti,
            JNIEnv* env,
            jthread thr,
            jobject object){

    jvmtiThreadInfo info;

    //获取启动线程信息
    (*jvmti)->GetThreadInfo(jvmti, thr, &info);
    //printf("[%-60s] MonitorContendedEntered\n", info.name);

    (*jvmti)->Deallocate(jvmti, (unsigned char*)&info);
}
*/

JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM* vm, char *options, void *reserved){

  jvmtiEnv *jvmti = NULL;
  jvmtiError error;

  //获取JVMTI environment
  error = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_1_2);
  if (error != JNI_OK) {
      fprintf(stderr, "ERROR: Couldn't get JVMTI environment");
      return JNI_ERR;
  }

  //注册功能
  jvmtiCapabilities capabilities;
  (void)memset(&capabilities, 0, sizeof(jvmtiCapabilities));

  capabilities.can_get_current_thread_cpu_time     =  1 ;
  capabilities.can_get_thread_cpu_time             =  1 ;
  capabilities.can_generate_monitor_events	  =  1 ;

  error = (*jvmti)->AddCapabilities(jvmti, &capabilities);

  checkError(error,"ERROR: Unable to AddCapabilities JVMTI");

  //jvmtiEventThreadStart ThreadStart;
   //jvmtiEventThreadEnd ThreadEnd;
  //设置JVM事件回调
  jvmtiEventCallbacks callbacks;
  callbacks.ThreadStart = &callbackThreadStart;
  callbacks.ThreadEnd = &callbackThreadEnd;
  /*callbacks.MonitorWait = &MonitorWait;
  callbacks.MonitorWaited = &MonitorWaited;
  callbacks.MonitorContendedEnter = &MonitorContendedEnter;
  callbacks.MonitorContendedEntered = &MonitorContendedEntered;*/
  error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
  checkError(error, "ERROR: Unable to SetEventCallbacks JVMTI!");

  //设置事件通知
  error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_THREAD_START, (jthread)NULL);
  checkError(error, "ERROR: Unable to SetEventCallbacks JVMTI!");

  error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_THREAD_END, (jthread)NULL);
  checkError(error, "ERROR: Unable to SetEventCallbacks JVMTI!");

  /*error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_MONITOR_WAIT, (jthread)NULL);
  checkError(error, "ERROR: Unable to SetEventCallbacks JVMTI!");

  error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_MONITOR_WAITED, (jthread)NULL);
  checkError(error, "ERROR: Unable to SetEventCallbacks JVMTI!");

  error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, (jthread)NULL);
  checkError(error, "ERROR: Unable to SetEventCallbacks JVMTI!");

  error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, (jthread)NULL);
  checkError(error, "ERROR: Unable to SetEventCallbacks JVMTI!");*/

  return JNI_OK;
}

JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char *options, void *reserved){
  //do nothing
}


JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *vm){
  //do nothing
}
